#include "g_local.h"
#include "../cola/cola_exec.h"
#include "../cola/cola_rtti.h"

// List of things that need to be swept in GC runs, other than threads:
// Clients

static colaProgram_t colaProgram;
static colaContext_t colaContext;

extern vmCvar_t	g_cola_gametype;

colaType_t *g_clientType;
colaType_t *g_gameControllerType;
	colaMethodDef_t *g_gcClientConnectMethod;
	colaMethodDef_t *g_gcClientJoinMethod;
	colaMethodDef_t *g_gcClientCommandMethod;

colaType_t *g_selectedGameControllerType;

colaExecObjectRef_t g_gameController;

static const char *g_requiredTypes[] =
{
	"Game:Client",
	"Game:GameController",
};

void G_ColaClientGTSA(colaUInt_t *size, colaUInt_t *alignment);

#define NUM_REQUIRED_TYPES	(sizeof(g_requiredTypes) / sizeof(g_requiredTypes[0]))


// **************** LOADER ****************

// Limit actual chunks to 256k
#define MAX_ALLOC	(256 * 1024)

static void *hostMalloc(colaSize_t size, colaMemoryVolatility_t vol, void *opaque, const char *file, int line)
{
	unsigned char *c;
	int *ip;

	if(size > MAX_ALLOC || size < 1)
		return NULL;

	// Allocate data
	c = trap_Alloc(size + 16, file, line);
	if(!c)
		return NULL;

	// Store size at the start of the block
	ip = (int *)c;
	*ip = size;

	return c + 16;
}

static void hostFree(void *p, void *opaque)
{
	char *c;
	if(p)
	{
		c = p;
		trap_Free(c - 16);
	}
}

static void *hostRealloc(void *p, colaSize_t size, void *opaque, const char *file, int line)
{
	unsigned char *c;
	int *ip;
	int originalSize;
	unsigned char *np;

	if(size > MAX_ALLOC || size < 0)
		return NULL;

	c = p;
	ip = ((int *)(c - 16));

	// 0 size = Free the object, return NULL
	if(!size)
	{
		trap_Free(c - 16);
		return NULL;
	}

	// If it's smaller, just leave it alone
	if(size < *ip)
		return p;

	// If it's bigger, allocate a new chunk
	np = trap_Alloc(size + 16, file, line);
	if(!np)
		return NULL;

	// Copy the old data in
	memcpy(np + 16, p, *ip);

	// Remove the old data
	trap_Free(c - 16);

	// Store the size
	ip = ((int *)np);
	*ip = size;

	// Return the new data buffer
	return np + 16;
}

static colaBool_t hostGetTypeSizeAndAlignment(const char *typeName, colaUInt_t *size, colaUInt_t *alignment)
{
	// Try built-in types
	if(colaDefaultGetTypeSizeAndAlignment(typeName, size, alignment))
		return COLA_TRUE;

	if(!Q_stricmp(typeName, "common:rtti:classid"))
	{
		colaClassIDGTSA(size, alignment);
		return COLA_TRUE;
	}
	if(!Q_stricmp(typeName, "game:client"))
	{
		G_ColaClientGTSA(size, alignment);
		return COLA_TRUE;
	}

	G_Error("Unknown type for GTSA: %s", typeName);

	return COLA_FALSE;
}

static const char *hostLoadTypeFile(void *hostRef, const char *name, void **freeHandle)
{
	char filename[512 + 10];
	char *c;
	fileHandle_t fh;
	int len;

	*freeHandle = NULL;

	// Reject huge class paths
	if(strlen(name) > 511)
		return NULL;

	// Reject anything not in common or game
	if(Q_stricmpn("common:", name, 7) && Q_stricmpn("game:", name, 5))
		return NULL;

	// Convert to a file
	strcpy(filename, "cola/");

	strcat(filename, name);

	c = filename;
	while(*c)
	{
		if(c[0] == ':')
			c[0] = '/';
		c++;
	}

	strcat(filename, ".cola");

	len = trap_FS_FOpenFile(filename, &fh, FS_READ);

	if(len == -1)
		return NULL;

	// Allocate a buffer for it
	c = (char *)trap_Alloc(len + 1, __FILE__, __LINE__);
	if(!c)
	{
		trap_FS_FCloseFile(fh);
		return NULL;
	}

	// Read the entire file
	trap_FS_Read(c, len, fh);
	c[len] = '\0';

	*freeHandle = c;

	trap_FS_FCloseFile(fh);

	return c;
}


static void hostFreeTypeFile(void *ptr)
{
	if(ptr)
		trap_Free(ptr);
}

static void hostEchoTypeCompilation(void *hostRef, const char *name)
{
	G_Printf("Compiling %s...\n", name);
}

static colaAPI_t *hostGetTypeAPI(const char *apiName)
{
	colaAPI_t *api;

	if(!Q_stricmp(apiName, "Game:Server"))
		return G_ColaServerAPI();
	if(!Q_stricmp(apiName, "Game:Client"))
		return G_ColaGameClientAPI();
	if(!Q_stricmp(apiName, "Common:RTTI:Object"))
		return colaDefaultMObjectAPI();
	if(!Q_stricmp(apiName, "Common:RTTI:ClassID"))
		return colaDefaultClassIDAPI();

	api = colaDefaultAPI(apiName);
	if(!api)
		G_Error("No host API for %s\n", apiName);

	return api;
}

void G_ColaInit( void )
{
	colaCompilerCallbacks_t callbacks;
	colaMemoryManager_t mm;
	colaErrorInfo_t ei;

	// Initialize the Cola server interface
	G_ColaInitServer();

	// Set up program state
	memset(&ei, 0, sizeof(colaErrorInfo_t));

	mm.malloc = hostMalloc;
	mm.free = hostFree;
	mm.realloc = hostRealloc;
	colaInitProgram(&colaProgram, &mm, &ei);

	// Initialize the main execution context
	colaInitializeContext(&colaProgram, &mm, &colaContext);
}

static void G_ColaError(const char *str, colaErrorInfo_t *ei)
{
	if(ei->errorCode == COLA_ERROR_GENERAL)
		G_Error("%s\nGeneral error: %s\nType: %s\nLine: %i\nInternal file: %s\nInternal line: %i\n", str, ei->errMsg, ei->type, ei->line, ei->iFile, ei->iLine);
	else if(ei->errorCode == COLA_ERROR_UNIMPLEMENTED_OPERATION)
		G_Error("%s\nUnimplemented operation: %s (%i)", str, ei->iFile, ei->iLine);
	else if(ei->errorCode == COLA_ERROR_INTERNAL)
		G_Error("%s\nCXE Internal Error: %s (%i)", str, ei->iFile, ei->iLine);
	else
		G_Error("%s\nError code: %i\nType: %s\nLine: %i", str, ei->errorCode, ei->type, ei->line);
}

#define SAFECALL(str, c)	\
	do {\
		if( (c) != COLA_EXEC_OK)\
			G_ColaError(str, &ei);\
	} while(0)

#define GASSERT(n, err)	\
	if(!(n))\
		G_Error(err)
		

void G_ColaLoadRelevant( void )
{
	colaCompilerCallbacks_t callbacks;
	colaMemoryManager_t mm;
	colaErrorInfo_t ei;
	colaBool_t okay;
	colaMethodDef_t *mdef;
	colaLookupParameter_t luParams[10];
	colaThread_t thread;
	colaExecStackVar_t *oldPSP;
	char gcTypeName[128+16];
	char *cp;
	colaInteger_t test[3];
	void *testPtrs[2];

	memset(&ei, 0, sizeof(colaErrorInfo_t));

	mm.malloc = hostMalloc;
	mm.free = hostFree;
	mm.realloc = hostRealloc;

	// Set up compiler callbacks
	memset(&callbacks, 0, sizeof(colaCompilerCallbacks_t));
	callbacks.getTypeSizeAndAlignment = hostGetTypeSizeAndAlignment;
	callbacks.getTypeAPI = hostGetTypeAPI;
	callbacks.loadTypeFile = hostLoadTypeFile;
	callbacks.freeTypeFile = hostFreeTypeFile;
	callbacks.echoTypeCompilation = hostEchoTypeCompilation;
	callbacks.hostRef = NULL;

	if(strlen(g_cola_gametype.string) > 128)
		G_Error("Gametype name is too long");

	// Load built-in types
	G_Printf("--- Cola XE: Loading base types...\n");
	okay = colaLoadTypes(&ei, &colaProgram, g_requiredTypes, NUM_REQUIRED_TYPES, NULL, &callbacks);
	if(!okay)
		G_ColaError("Script load failed!", &ei);

	// Get built-in types
	GASSERT(g_clientType = colaLookUpType(&colaProgram, "Game:Client"), "Client type lookup failed");

	GASSERT(g_gameControllerType = colaLookUpType(&colaProgram, "Game:GameController"), "GameController type lookup failed");
		luParams[0].ptype = COLA_PARAMETERTYPE_VALUE;
		luParams[0].t = g_clientType;
		GASSERT(g_gcClientJoinMethod = colaLookUpMethod(g_gameControllerType, COLA_METHODCATEGORY_STANDARD, "ClientJoin", luParams, 1, NULL), "ClientJoin not found");
		// ClientConnect and ClientCommand have the same parameter lists
		GASSERT(g_gcClientConnectMethod = colaLookUpMethod(g_gameControllerType, COLA_METHODCATEGORY_STANDARD, "ClientConnect", luParams, 1, NULL), "ClientConnect not found");
		GASSERT(g_gcClientCommandMethod = colaLookUpMethod(g_gameControllerType, COLA_METHODCATEGORY_STANDARD, "ClientCommand", luParams, 1, NULL), "ClientCommand not found");

	// Load the GameController
	G_Printf("--- Cola XE: Loading gametype...\n");

	// Get the gametype name
	strcpy(gcTypeName, "Game:Gametypes:");
	strcat(gcTypeName, g_cola_gametype.string);

	// Load it
	cp = gcTypeName;
	okay = colaLoadTypes(&ei, &colaProgram, (const char **) &cp, 1, NULL, &callbacks);

	if(!okay)
		G_ColaError("Script load failed!", &ei);

	g_selectedGameControllerType = colaLookUpType(&colaProgram, gcTypeName);
	if(!g_selectedGameControllerType)
		G_Error("Gametype lookup failed");
	if(!colaClassExtends(g_selectedGameControllerType, g_gameControllerType))
		G_Error("Gametype class is not a GameController");

	// Create a new thread to initialize the object
	colaInitializeThread(&ei, &colaContext, &thread);

	// Create the game controller
	SAFECALL("GameController creation failed", colaCreateObjectInitialized(&ei, &thread, g_selectedGameControllerType, &g_gameController));

	G_EchoAssociations();

	G_Printf("--- Cola XE: Success!\n");
}

// ***********************************************************
void G_ColaClientCreate( gclient_t *client )
{
	colaExecObjectRef_t obj;
	colaErrorInfo_t ei;
	colaThread_t thread;
	void *params[10];

	// Set up an execution thread
	memset(&ei, 0, sizeof(ei));
	colaInitializeThread(&ei, &colaContext, &thread);

	// Create the client
	SAFECALL("Client creation failed", colaCreateObjectInitialized(&ei, &thread, g_clientType, &obj));
	G_LinkClientToObject(client, obj);

	G_Printf("Created client object %x\n", obj);

	// Signal the client connect to the GameController
	params[0] = &obj;
	SAFECALL("ClientConnect failed", colaCallMethod(&ei, &thread, g_gcClientCommandMethod, g_gameController, params, NULL));
}

void G_ColaClientJoin( gclient_t *client )
{
	colaExecObjectRef_t obj;
	colaErrorInfo_t ei;
	colaThread_t thread;
	void *params[10];

	// Set up an execution thread
	memset(&ei, 0, sizeof(ei));
	colaInitializeThread(&ei, &colaContext, &thread);

	// Create the client
	SAFECALL("Client creation failed", colaCreateObjectInitialized(&ei, &thread, g_clientType, &obj));
	G_LinkClientToObject(client, obj);

	G_Printf("Created client object %x\n", obj);

	// Signal the client connect to the GameController
	params[0] = &obj;
	SAFECALL("ClientConnect failed", colaCallMethod(&ei, &thread, g_gcClientCommandMethod, g_gameController, params, NULL));
}
